/**
 *
 * 这个java文件对应 06-unicorn原生hook 笔记，其中
 *  6. 寄存器跳过代码
 *  7. 修改寄存器的值
 *  这两部分貌似不是处理 xiaojianbang.so 的，因此暂时跳过
 *
 */
package com.Unidbg_hengdin.ndk;

import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.*;
import com.github.unidbg.arm.context.Arm64RegisterContext;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.InstrumentCallback;
import com.github.unidbg.hook.hookzz.WrapCallback;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import unicorn.ArmConst;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

public class Study06 {

    public final AndroidEmulator emulator;
    public final VM vm;
    public final Module module;

    public final DvmClass NativeHelperUtils;
    public final boolean logging;

    Study06(boolean logging) {
        this.logging = logging;

        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("com.xiaojianbang.app")
                .addBackendFactory(new Unicorn2Factory(true))
                .build();
        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM() ;
        vm.setVerbose(logging);
        vm.setJni(new AbstractJni() {
            @Override
            public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
                System.out.println("signature: " + signature);
                if(signature.equals("java/lang/String->getBytes(Ljava/lang/String;)[B")){
                    String args = (String)dvmObject.getValue();
                    System.out.println("args :" + args);
                    byte[] byteArray = args.getBytes();
                    return new ByteArray(vm, byteArray);
                }
                return super.callObjectMethodV(vm, dvmObject, signature, vaList);
            }
        });
//        System.out.println("----000000000000");
        DalvikModule dmA = vm.loadLibrary(new File("unidbg-android/src/test/java/com/Unidbg_hengdin/ndk/libxiaojianbangA.so"),false);
        dmA.callJNI_OnLoad(emulator);
//        System.out.println("----1111111111111111111");
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/Unidbg_hengdin/ndk/libxiaojianbang.so"),false);
//        System.out.println("----222222222222222222222");
//        dm.callJNI_OnLoad(emulator);
//        System.out.println("----333333333333");
        module = dm.getModule();
        NativeHelperUtils = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");

//        System.out.println("----1555555555555");

    }
    void destroy() {
        IOUtils.close(emulator);
        if(logging){
            System.out.println("Study destroy");
        }
    }

    public static void main(String[] args) throws Exception {
        Study06 test = new Study06(true);
//        test.wrapFun();
//        test.instrumentFun();
//        test.replaceFun();
//        test.unicornFun();
//        test.unicornStackFun();
//        test.dynamicDebugging();
        test.MemFun();
//        test.MemFun2();
        test.destroy();
    }

    void wrapFun() {
        if (logging) {
            System.out.println("wrapFun1111111111111111");
        }
        HookZz hookzz = HookZz.getInstance(emulator);
        hookzz.enable_arm_arm64_b_branch();
        hookzz.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<Arm64RegisterContext>() {
            Pointer md5_ctx;
            @Override
            public void preCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
                md5_ctx = ctx.getPointerArg(0);
                Pointer plainText_ctx = ctx.getPointerArg(1);
                int length = ctx.getIntArg(2);
                Inspector.inspect(md5_ctx.getByteArray(0,64),"md5update md5ctx");
                Inspector.inspect(plainText_ctx.getByteArray(0,16),"md5update  plaintext");
            }
            @Override
            public void postCall(Emulator<?> emulator, Arm64RegisterContext ctx,HookEntryInfo info) {
                Inspector.inspect(md5_ctx.getByteArray(0,64),"md5update md5ctx");
            }
        });
//
//        StringObject md5_retval = NativeHelperUtils.callStaticJniMethodObject(emulator,"_Z9MD5UpdateP7MD5_CTXPhj",new StringObject(vm,"xichanganyihao"));
//        System.out.println("md5_retval :" + md5_retval.getValue());
        Symbol symbol = module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_add", false);

        hookzz.wrap(symbol,new WrapCallback<Arm64RegisterContext>() {
            @Override
            public void preCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
                int first = ctx.getIntArg(0);
                int second = ctx.getIntArg(1);
                int third = ctx.getIntArg(2);
                System.out.println("first " + first);
                System.out.println("second " + second);
                System.out.println("third " + third);

            }

        });

        Number[] numbers = new Number[]{symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelperUtils), 0x100, 0x200, 0x300)};
        //        Number res = NativeHelperUtils.callStaticJniMethodInt(emulator, "add(III)I", 0x100,0x200,0x300);

        System.out.println("add res:" + numbers[0].intValue());

    }

    public void instrumentFun(){
        System.out.println("dbiCall");
        HookZz hookzz = HookZz.getInstance(emulator);
        Symbol symbol = module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_add",false);
        hookzz.instrument(module.base + 0x1AEC,new InstrumentCallback<Arm64RegisterContext>() {

            @Override
            public void dbiCall(Emulator<?> emulator,Arm64RegisterContext ctx,HookEntryInfo info){
                System.out.println("dbiCall");
                System.out.println("w8 " + Integer.toHexString(ctx.getXInt(8)));
                System.out.println("w9 " + Integer.toHexString(ctx.getXInt(9)));
            }
        }

        );
        Number[] numbers =new Number[]{symbol.call(emulator,vm.getJNIEnv(),vm.addLocalObject(NativeHelperUtils),0x1,0x2,0x3)};
        System.out.println("add res:" + numbers[0].intValue());
    }

    public void replaceFun(){
        HookZz hookZz = HookZz.getInstance(emulator);
        hookZz.replace(module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5"), new ReplaceCallback(){
            @Override
            public HookStatus onCall(Emulator<?> emulator,long originFunction){
//                return super.onCall(emulator,originFunction);
//                return HookStatus.RET(emulator,originFunction);
//                int hashCode = vm.addLocalObject(new StringObject(vm,"replaceFun changed!"));
//                return HookStatus.LR(emulator,hashCode);

                return HookStatus.LR(emulator,996);
            }

        });
        // 方式一：不生效，resObject 为null
//        Symbol symbol = module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5");
//        Number[] numbers = new Number[]{ symbol.call(emulator,vm.getJNIEnv(),vm.addLocalObject(new StringObject(vm,"123456")))};
//        StringObject resObject = (StringObject) vm.getObject(numbers[0].intValue());
//        String md5Res = resObject.getValue().toString();
//        System.out.println("md5Res = " + md5Res);

        Symbol symbol = module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5");
        Number[] numbers = new Number[]{ symbol.call(emulator,vm.getJNIEnv(),vm.addLocalObject(new StringObject(vm,"123456")))};
        DvmObject resObject = vm.getObject(numbers[0].intValue());
        int md5Res = (int)(resObject.getValue());
        System.out.println("md5Res = " + md5Res);
//
//        System.out.println("--------------" );

        // 方式二：
//        StringObject md5_res = NativeHelperUtils.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"123456"));
//        System.out.println("md5_res res : " + md5_res.getValue());

        Integer md5ResInt = NativeHelperUtils.callStaticJniMethodInt(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"123456"));
        System.out.println("md5_res res : " + md5ResInt.intValue());
    }

    public void unicornFun(){
        System.out.println("unicornFun");
        emulator.getBackend().hook_add_new(new CodeHook() {
            @Override
            public void hook(Backend backend, long address, int size, Object user) {
                System.out.println("unicorn");
                System.out.println("address = " + address);
                System.out.println("address = " + Long.toHexString(address));
                System.out.println("size = " + size);
                Arm64RegisterContext context = emulator.getContext();
                if(address==module.base + 0x1FF4){
                    Pointer md5Ctx = context.getPointerArg(1);
                    Inspector.inspect(md5Ctx.getByteArray(0,32),"plainText 0x1FF4");
                    int length = (int)context.getIntArg(2);
                    Inspector.inspect("length: ",length);
                }else if(address== module.base + 0X2004){
                    Pointer md5Ctx = context.getXPointer(1);
                    Inspector.inspect(md5Ctx.getByteArray(0,32),"plainText 0X2004");
                    Pointer cipherText = context.getPointerArg(1);
                    Inspector.inspect(cipherText.getByteArray(0,32),"cipherText ");
                }
            }

            @Override
            public void onAttach(UnHook unHook) {

            }

            @Override
            public void detach() {

            }
        }, module.base +0X1FD8,module.base +0X2008,"hengdin" );

        StringObject md5Res = NativeHelperUtils.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"123456"));
        System.out.println("md5_res res : " + md5Res.getValue());

    }

    public void unicornStackFun(){
        // 输出调用栈
            System.out.println("unicornFun");
            emulator.getBackend().hook_add_new(new CodeHook() {
                @Override
                public void hook(Backend backend, long address, int size, Object user) {
                    System.out.println("unicorn");
                    System.out.println("address = " + address);
                    System.out.println("address = " + Long.toHexString(address));
                    System.out.println("size = " + size);
                    System.out.println("size = " + user);
                    System.out.println("=================================");
                    emulator.getUnwinder().unwind();
                    System.out.println("=================================");
                    Arm64RegisterContext context = emulator.getContext();
                    if(address==module.base + 0x1FF4){
                        Pointer md5Ctx = context.getPointerArg(1);
                        Inspector.inspect(md5Ctx.getByteArray(0,32),"plainText 0x1FF4");

                        int length = (int)context.getIntArg(2);
                        Inspector.inspect("length: ",length);

                    }else if(address== module.base + 0X2004){
                        Pointer md5Ctx = context.getXPointer(1);
                        Inspector.inspect(md5Ctx.getByteArray(0,32),"plainText 0X2004");
                        Pointer cipherText = context.getPointerArg(1);
                        Inspector.inspect(cipherText.getByteArray(0,32),"cipherText ");
                    }
                }

                @Override
                public void onAttach(UnHook unHook) {

                }

                @Override
                public void detach() {

                }
                // 注意：如果在函数第一级下打印堆栈是不会打印出有效的堆栈信息的，输出如下： Decode backtrace finish
//            }, module.base +0X1FD8,module.base +0X2008,"hengdin" );

            }, module.base +0x22A0,module.base +0x22A0,"hengdin" );

            StringObject md5Res = NativeHelperUtils.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"123456"));
            System.out.println("md5_res res : " + md5Res.getValue());

        }

    public void dynamicDebugging(){
        //  unidbg中的动态调试
        Debugger debugger = emulator.attach();
        debugger.addBreakPoint(module.base + 0x1AE4);
        debugger.addBreakPoint(module.base + 0x1AF4);
        int res = NativeHelperUtils.callStaticJniMethodInt(emulator, "add(III)I", 0x100,0x200,0x300);

        System.out.println("add res:" + Integer.toHexString(res));

        debugger.addBreakPoint(module.base + 0x22A0);
        debugger.addBreakPoint(module.base + 0x3A78);

        StringObject md5Res = NativeHelperUtils.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"123456"));
        System.out.println("md5_res res : " + md5Res.getValue());
    }
    public void MemFun(){
        String traceFile = "trace.txt";
        PrintStream traceStream ;
        try{
            traceStream = new PrintStream(new FileOutputStream(traceFile),true);
        }catch (FileNotFoundException e){
            throw new RuntimeException(e);
        }
        // 正常使用的时候，可以把监控范围缩小。
        // traceRead 在处理 add 函数时不输出内存信息，traceCode 都可以
//        emulator.traceRead(module.base, module.base+ module.size).setRedirect(traceStream);
        emulator.traceCode(module.base, module.base+ module.size).setRedirect(traceStream);
        // 调用函数,触发内存读写
        System.out.println(Long.toHexString(module.base));
        System.out.println(Long.toHexString(module.base + module.size));
        StringObject md5Res = NativeHelperUtils.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"123456"));
        System.out.println("md5_retval res : " + md5Res.getValue());
//        int addRes = NativeHelperUtils.callStaticJniMethodInt(emulator,"add(III)I",0x1,0x2,0x3);
//        System.out.println("add res:" + Integer.toHexString(addRes));
    }

    public void MemFun2(){
        String fileStream = "trace.log";
        PrintStream traceStream = null;
        try {
            traceStream = new PrintStream(new FileOutputStream(fileStream),true);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);
        int retval = NativeHelperUtils.callStaticJniMethodInt(emulator, "add(III)I", 0x100, 0x200, 0x300); // 执行Jni方法
        System.out.println("res : 0x" + Integer.toHexString(retval));
    }

    public void ContinueRegister(){
        emulator.getBackend().hook_add_new(new CodeHook() {
            @Override
            public void hook(Backend backend, long address, int size, Object user) {
                System.out.println(Long.toHexString(address));
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC,address+4+1);
            }

            @Override
            public void onAttach(UnHook unHook) {

            }

            @Override
            public void detach() {

            }
        },module.base+0xAD6,module.base+0xAD6,null);
    }
}
